
package com.trydofor.id.core;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * an immutable range of long value.
 * 
 * @author : Shi Rongjiu (www.trydofor.com)
 * @since 0.9
 */
public final class Range {
    
    private final long    startWith;
    private final long    endupWith;
    private final boolean includeStart;
    private final boolean includeEndup;
    //
    private final long    minimum;
    private final long    maximum;
    private final boolean includeMinimum;
    private final boolean includeMaximum;
    
    /**
     * create an immutable range.
     * 
     * @param start the start-with value(include).
     * @param endup the endup-with value(include).
     */
    public Range(long start, long endup) {
        this(start, true, endup, true);
    }
    
    /**
     * create an immutable range.
     * 
     * @param start the start-with value.
     * @param withStart true if include start-with.
     * @param endup the endup-with value.
     * @param withEndup true if include endup-with.
     */
    public Range(long start, boolean withStart, long endup, boolean withEndup) {
        startWith = start;
        endupWith = endup;
        includeStart = withStart;
        includeEndup = withEndup;
        
        if (startWith < endupWith) {
            minimum = startWith;
            maximum = endupWith;
            includeMinimum = includeStart;
            includeMaximum = includeEndup;
        }
        else {
            minimum = endupWith;
            maximum = startWith;
            includeMinimum = includeEndup;
            includeMaximum = includeStart;
        }
    }
    
    /**
     * get the start with value.
     * 
     * @return start with value.
     */
    public long startWith() {
        return startWith;
    }
    
    /**
     * get the end up with value.
     * 
     * @return end up with value.
     */
    public long endupWith() {
        return endupWith;
    }
    
    /**
     * @return the includeStart
     */
    public boolean includeStart() {
        return includeStart;
    }
    
    /**
     * @return the includeEndup
     */
    public boolean includeEndup() {
        return includeEndup;
    }
    
    /**
     * @return the includeMinimum
     */
    public boolean includeMinimum() {
        return includeMinimum;
    }
    
    /**
     * @return the includeMaximum
     */
    public boolean includeMaximum() {
        return includeMaximum;
    }
    
    /**
     * get the minimum of the range.
     * 
     * @return the minimum.
     */
    public long minimum() {
        return minimum;
    }
    
    /**
     * get the maximum of the range.
     * 
     * @return the maximum.
     */
    public long maximum() {
        return maximum;
    }
    
    /**
     * test whether this range contains the specified value.
     * 
     * @param x the specified type.
     * @return true if contains.
     */
    public boolean contains(long x) {
        boolean gmin = includeMinimum ? minimum <= x : minimum < x;
        boolean gmax = includeMaximum ? maximum >= x : maximum > x;
        return gmin && gmax;
    }
    
    /**
     * test whether this range contains all value of the specified range.
     * 
     * @param x @Nullable the specified range.
     * @return true if contains all.
     */
    public boolean containsAll(@Nullable Range x) {
        if (x == null) return false;
        return contains(x.minimum) && contains(x.maximum);
    }
    
    /**
     * test whether this range contains any value of the specified range.
     * 
     * @param x @Nullable the specified Range value.
     * @return true if contains any.
     */
    public boolean containsAny(@Nullable Range x) {
        if (x == null) return false;
        return contains(x.minimum) || contains(x.maximum) || x.contains(minimum);
    }
    
    /**
     * get a negative range with new.startWith=-old.endupWith, new.endupWith=-old.startWith.
     * the value between -Long.MAX_VALUE-1 and Long.MIN_VALUE can not negative.
     * 
     * @return @Nullable the negative range ,null if can not negative
     */
    @Nullable
    public Range negative() {
        long limit = -Long.MAX_VALUE;
        if (startWith < limit || endupWith < limit) {
            return null;
        }
        return new Range(-endupWith, includeEndup, -startWith, includeStart);
    }
    
    /**
     * get the string of 'minimum [<=|<] $variable and $variable [<=|<] maximum'
     * 
     * @param name @Nonnull the variable
     * @return @Nonnull the between-and string
     */
    @Nonnull
    public String toBetweenAnd(@Nonnull String name) {
        StringBuilder sb = new StringBuilder();
        sb.append(minimum);
        sb.append(" <");
        if (includeMinimum) sb.append('=');
        sb.append(" ");
        sb.append(name);
        sb.append(" and ");
        sb.append(name);
        sb.append(" <");
        if (includeMaximum) sb.append('=');
        sb.append(" ");
        sb.append(maximum);
        return sb.toString();
    }
    
    /**
     * equals minimum and maximum.
     * 
     * @param x other Range
     * @return true if have the same minimum and maximum.
     */
    public boolean equalsMinMax(Range x) {
        if (this == x) {
            return true;
        }
        if (x == null) {
            return false;
        }
        
        if (minimum != x.minimum) {
            return false;
        }
        if (includeMinimum != x.includeMinimum) {
            return false;
        }
        if (maximum != x.maximum) {
            return false;
        }
        if (includeMaximum != x.includeMaximum) {
            return false;
        }
        return true;
    }
    
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (int) (endupWith ^ (endupWith >>> 32));
        result = prime * result + (includeEndup ? 1231 : 1237);
        result = prime * result + (includeStart ? 1231 : 1237);
        result = prime * result + (int) (startWith ^ (startWith >>> 32));
        return result;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof Range)) {
            return false;
        }
        Range other = (Range) obj;
        if (endupWith != other.endupWith) {
            return false;
        }
        if (includeEndup != other.includeEndup) {
            return false;
        }
        if (includeStart != other.includeStart) {
            return false;
        }
        if (startWith != other.startWith) {
            return false;
        }
        return true;
    }
    
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(includeStart ? '[' : '(');
        sb.append(startWith);
        sb.append(',');
        sb.append(endupWith);
        sb.append(includeEndup ? ']' : ')');
        return sb.toString();
    }
}
